﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using StillDesign.PhysX;

namespace Physics
{
	public static class ModelConvertor
	{
		
		private static Dictionary<Model, TriangleMeshDescription> cash = new Dictionary<Model, TriangleMeshDescription>();

		public static TriangleMeshDescription getMeshDescription(Model model)
		{

			if (cash.ContainsKey(model)) return cash[model];

			Matrix worldPosition = Matrix.CreateScale(new Vector3(0.3937f, 0.3937f, 0.3937f));
			List<Vector3> vertices = new List<Vector3>();
			List<int> indices = new List<int>();

			ModelConvertor.ExtractTrianglesFrom(model, vertices, indices, worldPosition);

			TriangleMeshDescription triangleMeshDesc = new TriangleMeshDescription();
			triangleMeshDesc.AllocateVertices<Vector3>(vertices.Count);
			triangleMeshDesc.AllocateTriangles<int>(indices.Count);

			triangleMeshDesc.VerticesStream.SetData(vertices.ToArray());
			triangleMeshDesc.TriangleStream.SetData(indices.ToArray());

			triangleMeshDesc.VertexCount = vertices.Count;
			triangleMeshDesc.TriangleCount = indices.Count / 3;

			cash.Add(model, triangleMeshDesc);

			return triangleMeshDesc;

		}

		public static ConvexMeshDescription getConvexMeshDescription(Model model)
		{
			Matrix worldPosition = Matrix.CreateScale(new Vector3(0.3937f, 0.3937f, 0.3937f));
			List<Vector3> vertices = new List<Vector3>();
			List<int> indices = new List<int>();

			ModelConvertor.ExtractTrianglesFrom(model, vertices, indices, worldPosition);

			ConvexMeshDescription triangleMeshDesc = new ConvexMeshDescription();
			triangleMeshDesc.AllocatePoints<Vector3>(vertices.Count);
			triangleMeshDesc.AllocateTriangles<int>(indices.Count);

			triangleMeshDesc.PointsStream.SetData(vertices.ToArray());
			triangleMeshDesc.TriangleStream.SetData(indices.ToArray());

			triangleMeshDesc.PointCount = vertices.Count;
			triangleMeshDesc.TriangleCount = indices.Count / 3;

			return triangleMeshDesc;
		}

		/// <summary>
		/// Extract the vertices and indices from the specified model
		/// </summary>
		/// <param name="vertices">Output the list of vertices</param>
		/// <param name="indices">Output the list of indices</param>
		/// <param name="worldPosition">The models world position or use Matrix.Identity for object space</param>
		private static void ExtractTrianglesFrom(Model modelToUse, List<Vector3> vertices, List<int> indices, Matrix worldPosition)
		{
			Matrix transform = Matrix.Identity;
			foreach (ModelMesh mesh in modelToUse.Meshes)
			{
				// If the model has bones the vertices have to be transformed by the bone position
				transform = Matrix.Multiply(GetAbsoluteTransform(mesh.ParentBone), worldPosition);
				ExtractModelMeshData(mesh, ref transform, vertices, indices);
			}
		}

		/// <summary>
		/// Transform by a bone position or Identity if no bone is supplied
		/// </summary>
		private static Matrix GetAbsoluteTransform(ModelBone bone)
		{
			if (bone == null)
			{
				return Matrix.Identity;
			}
			return bone.Transform * GetAbsoluteTransform(bone.Parent);
		}

		/// <summary>
		/// Get all the triangles from all mesh parts
		/// </summary>
		private static void ExtractModelMeshData(ModelMesh mesh, ref Matrix transform, List<Vector3> vertices, List<int> indices)
		{
			foreach (ModelMeshPart meshPart in mesh.MeshParts)
			{
				ExtractModelMeshPartData(meshPart, ref transform, vertices, indices);
			}
		}

		/// <summary>
		/// Get all the triangles from each mesh part (Changed for XNA 4)
		/// </summary>
		private static void ExtractModelMeshPartData(ModelMeshPart meshPart, ref Matrix transform, List<Vector3> vertices, List<int> indices)
		{
			// Before we add any more where are we starting from
			int offset = vertices.Count;

			// == Vertices (Changed for XNA 4.0)

			// Read the format of the vertex buffer
			VertexDeclaration declaration = meshPart.VertexBuffer.VertexDeclaration;
			VertexElement[] vertexElements = declaration.GetVertexElements();
			// Find the element that holds the position
			VertexElement vertexPosition = new VertexElement();
			foreach (VertexElement vert in vertexElements)
			{
				if (vert.VertexElementUsage == VertexElementUsage.Position &&
					vert.VertexElementFormat == VertexElementFormat.Vector3)
				{
					vertexPosition = vert;
					// There should only be one
					break;
				}
			}
			// Check the position element found is valid
			if (vertexPosition == null ||
				vertexPosition.VertexElementUsage != VertexElementUsage.Position ||
				vertexPosition.VertexElementFormat != VertexElementFormat.Vector3)
			{
				throw new Exception("Model uses unsupported vertex format!");
			}
			// This where we store the vertices until transformed
			Vector3[] allVertex = new Vector3[meshPart.NumVertices];
			// Read the vertices from the buffer in to the array
			meshPart.VertexBuffer.GetData<Vector3>(
				meshPart.VertexOffset * declaration.VertexStride + vertexPosition.Offset,
				allVertex,
				0,
				meshPart.NumVertices,
				declaration.VertexStride);
			// Transform them based on the relative bone location and the world if provided
			for (int i = 0; i != allVertex.Length; ++i)
			{
				Vector3.Transform(ref allVertex[i], ref transform, out allVertex[i]);
			}
			// Store the transformed vertices with those from all the other meshes in this model
			vertices.AddRange(allVertex);

			// == Indices (Changed for XNA 4)

			// Find out which vertices make up which triangles
			if (meshPart.IndexBuffer.IndexElementSize != IndexElementSize.SixteenBits)
			{
				// This could probably be handled by using int in place of short but is unnecessary
				throw new Exception("Model uses 32-bit indices, which are not supported.");
			}
			// Each primitive is a triangle
			short[] indexElements = new short[meshPart.PrimitiveCount * 3];
			meshPart.IndexBuffer.GetData<short>(
				meshPart.StartIndex * 2,
				indexElements,
				0,
				meshPart.PrimitiveCount * 3);

			// Each TriangleVertexIndices holds the three indexes to each vertex that makes up a triangle
			//TriangleVertexIndices[] tvi = new TriangleVertexIndices[meshPart.PrimitiveCount];
			for (int i = 0; i != meshPart.PrimitiveCount; ++i)
			{
				// The offset is because we are storing them all in the one array and the 
				// vertices were added to the end of the array.

				indices.Add((indexElements[i * 3 + 2] + offset));
				indices.Add((indexElements[i * 3 + 1] + offset));
				indices.Add((indexElements[i * 3 + 0] + offset));

			}
			
		}

	}

}
